#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>

#include "ff.h"
#include "unios.h"
#include "posix.h"

// 可以同时打开的文件数量
#define MAX_FILE_DESCRIPTORS 16

// 文件对照表
typedef struct st_posix_file_info {
	int fd;
	FIL fil;
} posix_file_info_t;

typedef struct st_vfs_time {
    int year, mon, day;
    int hour, min, sec;
} vfs_time_t;

// stdin, stdout, stderr 文件 fd 定义
#define fd_stdin  0
#define fd_stdout 1
#define fd_stderr 2

// 当前最大的 fd 从 stderr 后面开始
static int _fdmax = fd_stderr + 1;

// 所有的文件对照表信息
static posix_file_info_t _pfs[MAX_FILE_DESCRIPTORS] = {};

// FatFS 文件系统
FATFS _fatfs_emmc = {0};
FATFS _fatfs_sd = {0};

static const err_map_t _fatfs_err[] = {
    {FR_OK,                  "FR_OK",                  "Succeeded"},
    {FR_DISK_ERR,            "FR_DISK_ERR",            "A hard error occurred in the low level disk I/O layer"},
    {FR_INT_ERR,             "FR_INT_ERR",             "Assertion failed"},
    {FR_NOT_READY,           "FR_NOT_READY",           "The physical drive cannot work"},
    {FR_NO_FILE,             "FR_NO_FILE",             "Could not find the file"},
    {FR_NO_PATH,             "FR_NO_PATH",             "Could not find the path"},
    {FR_INVALID_NAME,        "FR_INVALID_NAME",        "The path name format is invalid"},
    {FR_DENIED,              "FR_DENIED",              "Access denied due to prohibited access or directory full"},
    {FR_EXIST,               "FR_EXIST",               "Access denied due to prohibited access"},
    {FR_INVALID_OBJECT,      "FR_INVALID_OBJECT",      "The file/directory object is invalid"},
    {FR_WRITE_PROTECTED,     "FR_WRITE_PROTECTED",     "The physical drive is write protected"},
    {FR_INVALID_DRIVE,       "FR_INVALID_DRIVE",       "The logical drive number is invalid"},
    {FR_NOT_ENABLED,         "FR_NOT_ENABLED",         "The volume has no work area"},
    {FR_NO_FILESYSTEM,       "FR_NO_FILESYSTEM",       "There is no valid FAT volume"},
    {FR_MKFS_ABORTED,        "FR_MKFS_ABORTED",        "The f_mkfs() aborted due to any problem"},
    {FR_TIMEOUT,             "FR_TIMEOUT",             "Could not get a grant to access the volume within defined period"},
    {FR_LOCKED,              "FR_LOCKED",              "The operation is rejected according to the file sharing policy"},
    {FR_NOT_ENOUGH_CORE,     "FR_NOT_ENOUGH_CORE",     "LFN working buffer could not be allocated"},
    {FR_TOO_MANY_OPEN_FILES, "FR_TOO_MANY_OPEN_FILES", "Number of open files > FF_FS_LOCK"},
    {FR_INVALID_PARAMETER,   "FR_INVALID_PARAMETER",   "Given parameter is invalid"},
};

const char* fatfs_err_comment(int err) {
	int count = sizeof(_fatfs_err)/sizeof(_fatfs_err[0]);
	for (int i = 0; i < count; i++) {
		if (err == _fatfs_err[i].err) return _fatfs_err[i].comment;
	}
	return "(Unregistered error)";
}

DWORD get_fattime( void ) {
	DWORD stamp;
	vfs_time_t tm;

	if (STD_ERR_OK == unios_rtcsec_get(&tm.year, &tm.mon, &tm.day, &tm.hour, &tm.min, &tm.sec, NULL)) {
		// sanity checks
		tm.year = (tm.year >= 1980) && (tm.year < 2108) ? tm.year : 1980;
		tm.mon = (tm.mon >= 1) && (tm.mon <= 12) ? tm.mon : 1;
		tm.day = (tm.day >= 1) && (tm.day <= 31) ? tm.day : 1;
		tm.hour = (tm.hour >= 0) && (tm.hour <= 23) ? tm.hour : 0;
		tm.min = (tm.min >= 0) && (tm.min <= 59) ? tm.min : 0;
		tm.sec = (tm.sec >= 0) && (tm.sec <= 59) ? tm.sec : 0;

		stamp = (tm.year - 1980) << 25 | tm.mon << 21 | tm.day << 16 | tm.hour << 11 | tm.min << 5 | tm.sec;
	} else {
		// default time stamp derived from ffconf.h
		stamp = ((DWORD) (FF_NORTC_YEAR - 1980) << 25 | (DWORD) FF_NORTC_MON << 21 | (DWORD) FF_NORTC_MDAY << 16);
	}

	return stamp;
}

int _link(const char *path1, const char *path2) {
	(void) path1; (void) path2;
	errno = ENOTSUP; /* Not supported */
	return (-1);
}

#ifdef USE_MICROBLAZE
// !!! posix - _link() 别名 _link_r(), 用于 microblaze stdc 库 !!!
__attribute__((alias("_link")))
int _link_r(const char *path1, const char *path2);
#endif

/**
 * remove a link to a file
 */
int _unlink(const char *path) {
	errno = f_unlink(path);
	if(FR_OK != errno) return -1;
	return 0;
}

// 用于 stdin/stdout/stderr 的收发器
static transceiver_t* _stdtrx = NULL;
void posix_set_std_transceiver(transceiver_t *trx) { _stdtrx = trx; }
transceiver_t* posix_get_std_transceiver() { return _stdtrx; }

// 用于调试日志输出的收发器
static transceiver_t *_logtrx = NULL;
void posix_set_log_transceiver(transceiver_t *trx) { _logtrx = trx; }
transceiver_t* posix_get_log_transceiver() { return _logtrx; }

FRESULT mount_fs(FATFS* fs, const char *vol) {
    if (FR_OK != f_mount(fs, vol, 1)) {
    	printf("Failed to mount '%s' ! Try to reformat it and remount ...\n", vol);

		// 格式化
		#if (FF_DEFINED < 80286)
    		// v4.x 版本
			// FRESULT f_mkfs (
			//     const TCHAR* path,	/* Logical drive number */
			//     BYTE opt,			/* Format option */
			//     DWORD au,			/* Size of allocation unit (cluster) [byte] */
			//     void* work,			/* Pointer to working buffer (null: use heap memory) */
			//     UINT len				/* Size of working buffer [byte] */
			// )
    		FRESULT fres = f_mkfs(vol, FM_EXFAT, 0, NULL, 0);
		#else
    		// v5.x 版本
    		// typedef struct {
    		//     BYTE fmt;			/* Format option (FM_FAT, FM_FAT32, FM_EXFAT and FM_SFD) */
    		//     BYTE n_fat;			/* Number of FATs */
    		//     UINT align;			/* Data area alignment (sector) */
    		//     UINT n_root;			/* Number of root directory entries */
    		//     DWORD au_size;		/* Cluster size (byte) */
    		// } MKFS_PARM;
			MKFS_PARM opt = {.fmt = FM_EXFAT, .n_fat = 0, .align = 0, .n_root = 0, .au_size = 0 };

    		// FRESULT f_mkfs (
    		//     const TCHAR *path,	/* Logical drive number */
    		//     const MKFS_PARM *opt,/* Format options */
    		//     void *work,			/* Pointer to working buffer (null: use len bytes of heap memory) */
    		//     UINT len				/* Size of working buffer [byte] */
    		// )
			#ifdef FILE_SYSTEM_INTERFACE_QSPI_FLASH
				FRESULT fres = f_mkfs(vol, &opt, NULL, 4096);
			#else
				FRESULT fres = f_mkfs(vol, &opt, NULL, 512);
			#endif
		#endif
		if (fres != FR_OK) {
			printf("Unable to format '%s' !\n", vol);
			return fres;
		}

		// 格式化之后，重新挂载
		fres = f_mount(fs, vol, 1);
		if (fres != FR_OK) {
			printf("Unable to remount '%s' !\n", vol);
			return fres;
		}
    }

    return FR_OK;
}

// 查找指定的 fd 对应的文件信息
// 当 fd = -1 可以查找到一个空闲的 fd
static posix_file_info_t* get_by_fd(int fd) {
	for(int i=0; i<MAX_FILE_DESCRIPTORS; i++) {
		if(_pfs[i].fd == fd) return &_pfs[i];
	}
	return NULL;
}

// !!! posix - open() !!!
int open(const char *pathname, int flags, ...) {
	posix_file_info_t* finfo = get_by_fd(-1);
	if(!finfo) return -1;
	flags = flags & 0xFFFF; // 去掉高位不支持的标志

	/*
	FatFS 文件打开标志：
	FA_OPEN_EXISTING = 0x00	打开文件。如果文件不存在，则打开失败。 ( 默认 )
	FA_READ			 = 0x01 指定读访问对象。可以从文件中读取数据。与 FA_WRITE 结合可以进行读写访问。
	FA_WRITE		 = 0x02	指定写访问对象。可以向文件中写入数据。与 FA_READ 结合可以进行读写访问。
	FA_CREATE_NEW	 = 0x04	创建一个新文件。如果文件已存在，则创建失败。
	FA_CREATE_ALWAYS = 0x08	创建一个新文件。如果文件已存在，则它将被截断并覆盖。
	FA_OPEN_ALWAYS   = 0x10	如果文件存在，则打开；否则，创建一个新文件。
	FA_OPEN_APPEND   = 0x30 以添加方式打开

	unistd 文件打开标志：
	O_RDONLY = 0
	O_WRONLY = 1
	O_RDWR   = 2
	O_APPEND = 0x0008
	O_CREAT  = 0x0200
	O_TRUNC  = 0x0400

	stdio 文件打开标志：
	r	= (O_RDONLY                     ) = (FA_READ) ( 默认 )
	r+	= (O_RDWR                       ) = (FA_READ | FA_WRITE)
	w	= (O_WRONLY | O_CREAT | O_TRUNC ) = (          FA_WRITE | FA_CREATE_ALWAYS)
	w+	= (O_RDWR   | O_CREAT | O_TRUNC ) = (FA_READ | FA_WRITE | FA_CREATE_ALWAYS)
	a	= (O_WRONLY | O_CREAT | O_APPEND) = (          FA_WRITE | FA_OPEN_ALWAYS)
	a+	= (O_RDWR   | O_CREAT | O_APPEND) = (FA_READ | FA_WRITE | FA_OPEN_ALWAYS)
	*/
	int fflags = FA_READ;
	/**/ if(flags == (O_RDONLY                     )) fflags = (FA_READ);
	else if(flags == (O_RDWR                       )) fflags = (FA_READ | FA_WRITE);
	else if(flags == (O_WRONLY | O_CREAT | O_TRUNC )) fflags = (          FA_WRITE | FA_CREATE_ALWAYS);
	else if(flags == (O_RDWR   | O_CREAT | O_TRUNC )) fflags = (FA_READ | FA_WRITE | FA_CREATE_ALWAYS);
	else if(flags == (O_WRONLY | O_CREAT | O_APPEND)) fflags = (          FA_WRITE | FA_OPEN_ALWAYS);
	else if(flags == (O_RDWR   | O_CREAT | O_APPEND)) fflags = (FA_READ | FA_WRITE | FA_OPEN_ALWAYS);

	FRESULT fres = f_open(&finfo->fil, pathname, fflags);
	if(fres != FR_OK) return -1;

	finfo->fd = _fdmax++;

	return finfo->fd;
}

#ifdef USE_MICROBLAZE
// !!! posix - open() 别名 _open() !!!
__attribute__((alias("open")))
int _open(const char *pathname, int flags, ...);
#endif

// !!! posix - _close() !!!
int _close(int fd) {
	posix_file_info_t* finfo = get_by_fd(fd);
	if(!finfo) return -1;

	FRESULT fres = f_close(&finfo->fil);
	if(fres != FR_OK) return -1;

	memset(finfo, 0, sizeof(*finfo));
	finfo->fd = -1;
    return (0);
}

#ifdef USE_MICROBLAZE
__attribute__((alias("_close")))
int close(int fd);
#endif

// !!! posix - _read() !!!
ssize_t _read(int fd, unsigned char *buf, size_t count) {
	// 标准输入文件读 API 重定向
	if(fd == fd_stdin) {
		size_t rbytes = 0;
		rx_ret_t ret = _stdtrx->rx(_stdtrx, buf, count, &rbytes, -1, -1, NULL, RX_FLAG_NULL, NULL, RX_FLAG_NULL);
		return ret <= RX_RET_ERROR || ret >= RX_RET_MAX ? 0 : rbytes;
	}

	posix_file_info_t* finfo = get_by_fd(fd);
	if(!finfo) return -1;

	UINT rbytes = 0;
	FRESULT fres = f_read(&finfo->fil, buf, count, &rbytes);
	if(fres != FR_OK) return -1;

	return rbytes;
}

#ifdef USE_MICROBLAZE
// !!! posix - read() 别名 !!!
__attribute__((alias("_read")))
ssize_t read(int fd, unsigned char *buf, size_t count);
#endif

// !!! posix - _write() !!!
ssize_t _write(int fd, const void *buf, size_t count) {
	// 标准输出文件写 API 重定向
	if(fd == fd_stdout || fd == fd_stderr) {
		size_t tbytes = 0;
		std_err_t err = _stdtrx->tx(_stdtrx, (void*)buf, count, &tbytes);
		// 如果有 _logtrx，并且它未用作 stdout/stderr，则把输出到 stdout/stderr 的输出复制到 _logtrx，以便调试
		if(_logtrx && _stdtrx != _logtrx) _logtrx->tx(_logtrx, (void*)buf, count, &tbytes);
		return err ? 0 : tbytes;
	}

	posix_file_info_t* finfo = get_by_fd(fd);
	if(!finfo) return -1;

	UINT wbytes = 0;
	FRESULT fres = f_write(&finfo->fil, buf, count, &wbytes);
	if(fres != FR_OK) return -1;

	return wbytes;
}

#ifdef USE_MICROBLAZE
// !!! posix - write() 别名 !!!
__attribute__((alias("_write")))
ssize_t write(int fd, unsigned char *buf, size_t count);
#endif

// !!! posix - fsync() !!!
int fsync(int fd) {
	posix_file_info_t* finfo = get_by_fd(fd);
	if(!finfo) return -1;

	FRESULT fres = f_sync(&finfo->fil);
	if(fres != FR_OK) return -1;
	return 0;
}

// !!! posix - stat() !!!
int	_stat(const char *path, struct stat *st) {
	if(!path || !st) return -1;

	FILINFO fno;
	FRESULT fres = f_stat(path, &fno);
	if(fres != FR_OK) return -1;

	memset(st, 0, sizeof(*st));
	st->st_size = fno.fsize;
	st->st_mtime = fno.ftime;
	if(fno.fattrib & AM_DIR) st->st_mode |= _IFDIR;

	return 0;
}

#ifdef USE_MICROBLAZE
__attribute__((alias("_stat")))
int stat(const char *path, struct stat *st);
#endif

off_t _lseek(s32 fd, off_t offset, s32 whence) {
	posix_file_info_t* finfo = get_by_fd(fd);
	if(!finfo) return ((off_t)-1);

	/**/ if(whence == SEEK_END) offset += f_size(&finfo->fil);
	else if(whence == SEEK_CUR) offset += f_tell(&finfo->fil);
	else if(whence == SEEK_SET) offset = offset;
	else return ((off_t)-1);

	FRESULT fres = f_lseek(&finfo->fil, offset);
	if(fres != FR_OK) return ((off_t)-1);
	return offset;
}

#ifdef USE_MICROBLAZE
__attribute__((alias("_lseek")))
off_t lseek(s32 fd, off_t offset, s32 whence);
#endif

// 初始化 POSIX-FatFS 适配层
std_err_t posix_fatfs_init() {
	memset(_pfs, 0, sizeof(_pfs));
	for(int i=0; i<MAX_FILE_DESCRIPTORS; i++) {
		_pfs[i].fd = -1;
	}

	// 对于 OBS900 硬件，SDC0 对应的是 SD 卡插槽，未用 SDC1，所以 "/sd1" 对应的是 SD 卡的第 1 个分区
	// 各个磁盘卷标及顺序定义见 ffconf.h
	//   #define FF_VOLUME_STRS		"sd1","sd2"
	// 各个磁盘及分区定义见 ff.c
	//   PARTITION VolToPart[] = {...}
#if USE_PCBA_LC_ZU47DR || USE_PCBA_ALINX_ZU4EV
	FRESULT fr = mount_fs(&_fatfs_emmc, "/emmc1");
#elif USE_MICROBLAZE
	FRESULT fr = mount_fs(&_fatfs_sd,   "/qspi1");
#else
	FRESULT fr = mount_fs(&_fatfs_sd,   "/sd1");
#endif

	return (fr == FR_OK) ? STD_ERR_OK : STD_ERR_FAIL;
}
